package com.example.musicplayer.gobang;

public class AI_3 {
    //search depth
    private static final int DEPTH = 6;
    //mark means need to search
    private static final int MARKED = -2;
    //search range of location
    private static final int[] RANGE;
    private final int SIZE;
    private final Gobang mGobang;
    private final int[][] mScore;

    static {
        RANGE = new int[DEPTH];
        RANGE[0] = 27;
        RANGE[1] = 16;
        RANGE[2] = 16;
        RANGE[3] = 16;
        RANGE[4] = 16;
        RANGE[5] = 16;
    }

    public AI_3(Gobang gobang) {
        mGobang = gobang;
        SIZE = mGobang.getChessBoardSize();
        mScore = new int[SIZE][SIZE];
    }

    public Location nextLocation() {
        int[][] data = mGobang.copyChessLocation();
        int round = mGobang.getRound();

        Location location = checkFour(data, false);
        if (location != null) {
            return location;
        }
        location = checkFour(data, true);
        if (location != null) {
            return location;
        }

        int[][] checkLocation = updateScore(data, 0);
        int max = Integer.MIN_VALUE;
        int targetX = 0;
        int targetY = 0;
        for (int[] ints : checkLocation) {
            int x = ints[0];
            int y = ints[1];
            data[x][y] = round;
            int value = MaxMin(data, x, y, Gobang.getOppositeRound(round), Integer.MIN_VALUE, Integer.MAX_VALUE, 1);
            data[x][y] = Gobang.EMPTY;
            if (value > max) {
                max = value;
                targetX = x;
                targetY = y;
            }
        }
        return new Location(targetX, targetY);
    }

    private int MaxMin(int[][] data, int x, int y, int round, int alpha, int beta, int depth) {
        if (depth >= DEPTH || Gobang.isWin(data, x, y, Gobang.getOppositeRound(round))) {
            return evaluate(data, SIZE, mGobang.getRound());
        }
        int[][] checkLocation = updateScore(data, depth);
        for (int[] ints : checkLocation) {
            int checkX = ints[0];
            int checkY = ints[1];
            data[checkX][checkY] = round;
            int value = MaxMin(data, checkX, checkY, Gobang.getOppositeRound(round), alpha, beta, depth + 1);
            data[checkX][checkY] = Gobang.EMPTY;
            //if our round, get the max value. If opposite round, get the min value
            if (round == mGobang.getRound()) {
                alpha = Math.max(alpha, value);
            } else {
                beta = Math.min(beta, value);
            }
            //alpha-beta cut
            if (alpha >= beta) {
                if (round == mGobang.getRound()) {
                    return alpha;
                } else {
                    return beta;
                }
            }
        }
        if (round == mGobang.getRound()) {
            return alpha;
        }
        return beta;
    }

    //get each location's score
    private int[][] updateScore(int[][] data, int depth) {
        AI_2.calculateScore(data, mScore, SIZE);
        int[][] needCheckLocation;
        if (mGobang.getCounts() + depth >= SIZE * SIZE - RANGE[depth]) {
            needCheckLocation = new int[SIZE * SIZE - mGobang.getCounts() - depth][2];
            int index = 0;
            for (int i = 0; i < SIZE; i++) {
                for (int j = 0; j < SIZE; j++) {
                    if (data[i][j] == Gobang.EMPTY) {
                        needCheckLocation[index][0] = i;
                        needCheckLocation[index][1] = j;
                        index++;
                    }
                }
            }
        } else {
            needCheckLocation = new int[RANGE[depth]][2];
            int max = -1;
            int x = 0;
            int y = 0;
            int k = 1;
            while (k <= RANGE[depth]) {
                for (int i = 0; i < SIZE; i++) {
                    for (int j = 0; j < SIZE; j++) {
                        if (mScore[i][j] > max) {
                            max = mScore[i][j];
                            x = i;
                            y = j;
                        }
                    }
                }
                mScore[x][y] = MARKED;
                needCheckLocation[k - 1][0] = x;
                needCheckLocation[k - 1][1] = y;
                max = -1;
                x = 0;
                y = 0;
                k++;
            }
        }
        return needCheckLocation;
    }

    //check if there is four in a row
    private Location checkFour(int[][] data, boolean checkOpposite) {
        if (mGobang.getCounts() >= 7) {
            int preX = mGobang.getLocX(mGobang.getCounts() - 1);
            int preY = mGobang.getLocY(mGobang.getCounts() - 1);
            if (!checkOpposite) {
                preX = mGobang.getLocX(mGobang.getCounts() - 2);
                preY = mGobang.getLocY(mGobang.getCounts() - 2);
            }
            int checkChess = checkOpposite ? mGobang.getOppositeRound() : mGobang.getRound();
            int tempX;
            int tempY;
            //horizontal direction
            for (int i = -4; i <= 4; i++) {
                tempX = preX;
                tempY = preY + i;
                if (tempY >= 0 && tempY < SIZE && mGobang.getChess(tempX, tempY) == Gobang.EMPTY) {
                    data[tempX][tempY] = checkChess;
                    if (Gobang.isWin(data, tempX, tempY, checkChess)) {
                        data[tempX][tempY] = Gobang.EMPTY;
                        return new Location(tempX, tempY);
                    }
                    data[tempX][tempY] = Gobang.EMPTY;
                }
            }
            //vertical direction
            for (int i = -4; i <= 4; i++) {
                tempX = preX + i;
                tempY = preY;
                if (tempX >= 0 && tempX < SIZE && mGobang.getChess(tempX, tempY) == Gobang.EMPTY) {
                    data[tempX][tempY] = checkChess;
                    if (Gobang.isWin(data, tempX, tempY, checkChess)) {
                        data[tempX][tempY] = Gobang.EMPTY;
                        return new Location(tempX, tempY);
                    }
                    data[tempX][tempY] = Gobang.EMPTY;
                }
            }
            //forward slash direction
            for (int i = -4; i <= 4; i++) {
                tempX = preX + i;
                tempY = preY - i;
                if (tempX >= 0 && tempX < SIZE && tempY >= 0 && tempY < SIZE && mGobang.getChess(tempX, tempY) == Gobang.EMPTY) {
                    data[tempX][tempY] = checkChess;
                    if (Gobang.isWin(data, tempX, tempY, checkChess)) {
                        data[tempX][tempY] = Gobang.EMPTY;
                        return new Location(tempX, tempY);
                    }
                    data[tempX][tempY] = Gobang.EMPTY;
                }
            }
            //back slash direction
            for (int i = -4; i <= 4; i++) {
                tempX = preX - i;
                tempY = preY - i;
                if (tempX >= 0 && tempX < SIZE && tempY >= 0 && tempY < SIZE && mGobang.getChess(tempX, tempY) == Gobang.EMPTY) {
                    data[tempX][tempY] = checkChess;
                    if (Gobang.isWin(data, tempX, tempY, checkChess)) {
                        data[tempX][tempY] = Gobang.EMPTY;
                        return new Location(tempX, tempY);
                    }
                    data[tempX][tempY] = Gobang.EMPTY;
                }
            }
        }
        return null;
    }

    /**
     * evaluate current situation
     *
     * @param data  chessman location info
     * @param size  chessboard size
     * @param chess our chess is black or white
     * @return the Integer value to judge current situation
     */
    public static int evaluate(int[][] data, int size, int chess) {
        int ourScore = 0;
        int oppositeScore = 0;
        //save current chess location info
        int[] temp = new int[size];
        //the length of temp
        int length = size;
        //horizontal
        for (int i = 0; i < size; i++) {
            ourScore += getValue(data[i], size, chess);
            oppositeScore += getValue(data[i], size, Gobang.getOppositeRound(chess));
        }
        //vertical
        for (int j = 0; j < size; j++) {
            for (int i = 0; i < size; i++) {
                temp[i] = data[i][j];
            }
            ourScore += getValue(temp, size, chess);
            oppositeScore += getValue(temp, size, Gobang.getOppositeRound(chess));
        }
        //forward slash, the above part
        for (int i = 0; i < size; i++) {
            length = 0;
            for (int j = 0; i - j >= 0 && j < size; j++) {
                temp[j] = data[i - j][j];
                length++;
            }
            ourScore += getValue(temp, length, chess);
            oppositeScore += getValue(temp, length, Gobang.getOppositeRound(chess));
        }
        //forward slash, the below part
        for (int i = 1; i < size; i++) {
            length = 0;
            for (int j = 0; size - 1 - j >= 0 && i + j < size; j++) {
                temp[j] = data[size - 1 - j][i + j];
                length++;
            }
            ourScore += getValue(temp, length, chess);
            oppositeScore += getValue(temp, length, Gobang.getOppositeRound(chess));
        }
        //back slash, the above part
        for (int i = 0; i < size; i++) {
            length = 0;
            for (int j = 0; i + j < size; j++) {
                temp[j] = data[j][i + j];
                length++;
            }
            ourScore += getValue(temp, length, chess);
            oppositeScore += getValue(temp, length, Gobang.getOppositeRound(chess));
        }
        //back slash, the below part
        for (int i = 0; i < size; i++) {
            length = 0;
            for (int j = 0; i + j < size; j++) {
                temp[j] = data[i + j][j];
                length++;
            }
            ourScore += getValue(temp, length, chess);
            oppositeScore += getValue(temp, length, Gobang.getOppositeRound(chess));
        }
        return ourScore - oppositeScore;
    }

    //get the value of chess, size to check, color is chess
    private static int getValue(int[] arr, int size, int chess) {
        //the value
        int value = 0;
        //connected chess num
        int counts = 0;
        //the empty loc before and after connected chessmen
        int empty = 0;
        int i = 1;
        if (arr[0] == Gobang.EMPTY) {
            empty++;
        } else if (arr[0] == chess) {
            counts++;
        }
        while (i < size) {
            if (arr[i] == chess) {
                counts++;
            } else if (arr[i] == Gobang.EMPTY) {
                if (counts == 0) {
                    empty = 1;
                } else {
                    value += ScoreTable.getScore(counts, empty + 1);
                    empty = 1;
                    counts = 0;
                }
            } else {
                value += ScoreTable.getScore(counts, empty);
                empty = 0;
                counts = 0;
            }
            i++;
        }
        value += ScoreTable.getScore(counts, empty);
        return value;
    }

    private static class ScoreTable {
        //A-B, A means the num of connected chessmen，B means two sides' empty location's num
        public static final int FIVE = 100000000;//100000;
        public static final int FOUR_ZERO = 10000000;//10000;
        public static final int FOUR_ONE = 1000000;//2000;
        public static final int THREE_ZERO = 100000;//1500;
        public static final int THREE_ONE = 10000;//100;
        public static final int TWO_ZERO = 1000;//100;
        public static final int TWO_ONE = 100;//10;
        public static final int ONE_ZERO = 10;
        public static final int ONE_ONE = 1;
        public static final int OTHER = 0;

        /**
         * get score
         *
         * @param counts num of connected chessmen
         * @param empty  empty location in the two sides
         * @return the score
         */
        public static int getScore(int counts, int empty) {
            if (counts >= 5) {
                return FIVE;
            } else if (counts == 4) {
                if (empty == 2) {
                    return FOUR_ZERO;
                } else if (empty == 1) {
                    return FOUR_ONE;
                }
            } else if (counts == 3) {
                if (empty == 2) {
                    return THREE_ZERO;
                } else if (empty == 1) {
                    return THREE_ONE;
                }
            } else if (counts == 2) {
                if (empty == 2) {
                    return TWO_ZERO;
                } else if (empty == 1) {
                    return TWO_ONE;
                }
            } else if (counts == 1) {
                if (empty == 2) {
                    return ONE_ZERO;
                } else if (empty == 1) {
                    return ONE_ONE;
                }
            }
            return OTHER;
        }
    }
}